package net.xgs.rbac.endpoint;



import net.xgs.rbac.annotations.ClearNoLogin;
import net.xgs.rbac.annotations.NoLogin;
import net.xgs.rbac.config.PathRouteVo;
import net.xgs.rbac.config.RouteVo;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.web.mappings.HandlerMethodDescription;
import org.springframework.boot.actuate.web.mappings.MappingDescriptionProvider;
import org.springframework.boot.actuate.web.mappings.servlet.DispatcherServletMappingDescription;
import org.springframework.boot.actuate.web.mappings.servlet.DispatcherServletMappingDetails;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author xtgss xtgss007@gmail.com
 * @date 2020/3/30 16:56
 */
@Endpoint(id="rbac")
public class RbacEndPoint {
    private final Collection<MappingDescriptionProvider> descriptionProviders;
    private final ApplicationContext context;

    public RbacEndPoint(Collection<MappingDescriptionProvider> descriptionProviders, ApplicationContext context) {
        this.descriptionProviders = descriptionProviders;
        this.context = context;
    }

    @ReadOperation
    public RouteVo mappings() {
        RouteVo routeVo = new RouteVo();
        ApplicationContext target = this.context;
        while (target != null) {
            mappingsForContext(target,routeVo);
            target = target.getParent();
        }
        return routeVo;
    }

    @SuppressWarnings("unchecked")
    private void mappingsForContext(ApplicationContext applicationContext,RouteVo routeVo) {
        this.descriptionProviders.stream().map(provider -> provider.describeMappings(applicationContext))
                .filter(object -> object instanceof HashMap)
                .map(object -> (HashMap<String, List<DispatcherServletMappingDescription>>) object)
                .forEach(map -> {
                    Set<String> keys = map.keySet();
                    for (String key : keys) {
                        List<DispatcherServletMappingDescription> descriptionList = map.get(key);
                        for (DispatcherServletMappingDescription description : descriptionList) {
                            this.assembly(description.getDetails(),routeVo);
                        }
                    }
                });
    }

    private static final String[] FILTER_LIST = new String[]{"/actuator","/error"};

    private void assembly(DispatcherServletMappingDetails details,RouteVo routeVo) {
        if(details == null) {
            return;
        }
        Set<String> patterns = details.getRequestMappingConditions().getPatterns();
        String pattern = getPattern(patterns);
        if(isActuator(pattern)){
            return;
        }
        HandlerMethodDescription handlerMethod = details.getHandlerMethod();
        PathRouteVo build = PathRouteVo.builder().methodName(handlerMethod.getName())
                .className(handlerMethod.getClassName())
                .url(pattern)
                .check(judgeLogin(handlerMethod))
                .build();
        routeVo.add(build);

    }

    private String getPattern(Set<String> patterns){
        return patterns.stream().findFirst().orElse(null);
    }

    private boolean isActuator(String pattern){
        if(pattern == null) {
            return true;
        }

        for (String s : FILTER_LIST) {
            if(pattern.startsWith(s)){
                return true;
            }
        }
        return false;
    }

    /**
     * 是否需要登陆
     * @param handlerMethod xxx
     * @return boolean
     */
    private boolean judgeLogin(HandlerMethodDescription handlerMethod){
        try {
            Class<?> clazz = Class.forName(handlerMethod.getClassName());
            Method[] methods = clazz.getMethods();
            Method method = null;
            for (Method m : methods) {
                if(m.getName().equals(handlerMethod.getName())){
                    method = m;
                    break;
                }
            }

            if(method == null){
                return false;
            }

            System.out.println(this.getPath(clazz, method));

            ClearNoLogin clearNoLogin = method.getAnnotation(ClearNoLogin.class);
            NoLogin cNoLogin = clazz.getAnnotation(NoLogin.class);
            NoLogin mNoLogin = method.getAnnotation(NoLogin.class);

            return clearNoLogin != null || (cNoLogin == null && mNoLogin == null);

        } catch (Exception e) {
            return false;
        }

    }

    public String getPath(Class<?> clazz,Method method){
        Annotation[] annotations = clazz.getAnnotations();
        Annotation[] methodsAnnotations = method.getAnnotations();
        String clazzPath = this.getPathByMapping(annotations);
        System.out.println(String.format("class:%s,method:%s", clazz.getName(), method.getName()));
        String methodPath = this.getPathByMapping(methodsAnnotations);
        return String.format("%s%s",clazzPath,methodPath);
    }

    public String getPathByMapping(Annotation[] annotations){
        for (Annotation annotation : annotations) {
            Mapping mapping = annotation.annotationType().getAnnotation(Mapping.class);
            RequestMapping requestMapping = annotation.annotationType().getAnnotation(RequestMapping.class);
            if(mapping != null || requestMapping != null){
                try {
                    Method[] declaredMethods = annotation.getClass().getDeclaredMethods();
                    Method method = Arrays.stream(declaredMethods).filter(item -> "value".equals(item.getName())).findFirst().orElse(null);
                    if(method != null){
                        String[] values = (String[])method.invoke(annotation,  null);
                        return values[0];
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return "";
    }
}
